package com.qlogic.commons.utils.reflect;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;

import com.qlogic.commons.exceptions.BaseException;
import com.qlogic.commons.exceptions.BaseRuntimeException;
import com.qlogic.commons.utils.io.SerializationUtils;

public class Property implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 6993969861052089915L;
	public static final String GET = "get";
	public static final String SET = "set";

	/**
	 * Comment for <code>accessors</code>
	 */
	private String name;

	/**
	 * Comment for <code>accessors</code>
	 */
	private ArrayList accessors = new ArrayList (1);

	/**
	 * Comment for <code>setMethod</code>
	 */
	private transient Method setMethod;

	/**
	 * Comment for <code>getMethod</code>
	 */
	private transient Method getMethod;
	
	private transient Class type;

	/**
	 *  
	 */
	public Property() {
	}
	
	public Property createLike () {
		Property property = new Property ();
		createLike (property);
		return property;
	}

	public void createLike (Property property) {
		property.setName (name);
		property.setGetMethod (getMethod);
		property.setSetMethod (setMethod);
		property.setType (type);
		for (int i = 0; i < getAccessorsCount (); i++) {
			property.addAccessor (getAccessor (i).createLike ());
		}
	}
	public Property (Class clazz, String propName) {
		create (this, clazz, propName);
	}

	public static void create (Property instance, Class clazz, String propName) {
		if (instance == null) {
			instance = new Property ();
		}
		String property = propName;
		int indexOfDot = property.indexOf(".");
		String attribute = null;
		while (indexOfDot >= 0) {
			attribute = property.substring(0, indexOfDot);
			property = property.substring(indexOfDot + 1);
			indexOfDot = property.indexOf(".");
			Method attributeGetMethod = null;
			Method attributeSetMethod = null;
			try {
				attributeGetMethod = ReflectUtils.getAttributeGetMethod(
						attribute, clazz);
				attributeSetMethod = ReflectUtils.getAttributeSetMethod(
						attribute, clazz, attributeGetMethod
								.getReturnType());
				instance.addAccessor(new Property (attribute, attributeSetMethod, attributeGetMethod));
				clazz = attributeGetMethod.getReturnType();
			} catch (BaseException ex) {
				throw new BaseRuntimeException ("Class [" + clazz + ", "
								+ propName + "], can not load set and get methods", ex);
			}
		}
		try {
			instance.addAccessor(new Property (property, ReflectUtils.getAttributeSetMethod(
					property, clazz, ReflectUtils.getAttributeGetMethod(
							property, clazz).getReturnType()),
					ReflectUtils.getAttributeGetMethod(property, clazz)));
		} catch (BaseException ex) {
			throw new BaseRuntimeException ("Class [" + clazz + ", " 
								+ propName + "], can not load set and get methods", ex);
		}
		instance.setName (propName);
	}
	
	public static Property create (Class clazz, String propName) {
		Property instance = new Property ();
		create (instance, clazz, propName);
		return instance;
	}

	/**
	 * @param setMethod
	 * @param getMethod
	 */
	public Property(String name, Method setMethod, Method getMethod, Class type) {
		this.name = name;
		this.setMethod = setMethod;
		this.getMethod = getMethod;
		this.type = type;
	}

	/**
	 * @param setMethod
	 * @param getMethod
	 */
	public Property(String name, Method setMethod, Method getMethod) {
		this (name, setMethod, getMethod, null);
	}

	/**
	 * @param setMethod
	 * @param getMethod
	 */
	public Property(Method setMethod, Method getMethod) {
		this (null, setMethod, getMethod);
	}

	/**
	 * @param setMethod
	 * 
	 * @uml.property name="setMethod"
	 */
	public void setSetMethod(Method setMethod) {
		this.setMethod = setMethod;
	}

	/**
	 * @return
	 * 
	 * @uml.property name="setMethod"
	 */
	public Method getSetMethod() {
		return setMethod;
	}

	/**
	 * @param getMethod
	 * 
	 * @uml.property name="getMethod"
	 */
	public void setGetMethod(Method getMethod) {
		this.getMethod = getMethod;
	}

	/**
	 * @return
	 * 
	 * @uml.property name="getMethod"
	 */
	public Method getGetMethod() {
		return getMethod;
	}

	/**
	 * @param accessorSetMethod
	 * @param accessorGetMethod
	 */
	public void addAccessor(Method accessorSetMethod, Method accessorGetMethod) {
		accessors.add (new Property(accessorSetMethod, accessorGetMethod));
	}

	/**
	 * @param accessorSetMethod
	 * @param accessorGetMethod
	 */
	public void addAccessor(Property accessor) {
		accessors.add (accessor);
	}

	/**
	 * @return Property
	 */
	public Property getAccessor (int index) {
		return (Property) accessors.get(index);
	}

	/**
	 * @return Property
	 */
	public ArrayList getAccessors () {
		return accessors;
	}

	/**
	 * @return Property
	 */
	public Property getLastAccessor () {
		if (accessors == null || accessors.isEmpty ()) {
			return null;
		}
		return (Property) accessors.get(accessors.size () - 1);
	}

	/**
	 * @return Property
	 */
	public Class getType () {
		if (isSimpleProperty ()) {
			return getSimpleType ();
		}
		return getLastAccessor ().getSimpleType ();
	}

	/**
	 * @return Property
	 */
	public Class getSimpleType () {
		if (type != null) {
			return type;
		}
		if (getMethod == null) {
			return null;
		}
		return getMethod.getReturnType ();
	}

	/**
	 * @return Property
	 */
	public boolean isSimpleProperty () {
		return accessors == null || accessors.isEmpty ();
	}

	/**
	 * @return Property
	 */
	public boolean isSingleAccessorProperty () {
		return accessors != null && accessors.size () == 1;
	}

	/**
	 * @return
	 */
	public boolean isLastAccessor(int index) {
		return index == (accessors.size() - 1);
	}

	/**
	 * @return
	 */
	public int getAccessorsCount () {
		return accessors.size ();
	}
	
	/**
	 * @return has accessors
	 */
	public boolean hasAccessors () {
		return getAccessorsCount () > 0;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public void reset () {
		accessors = new ArrayList (1);
		setMethod = null;
		getMethod = null;
	}
	
	public String toString () {
		StringBuffer buff = new StringBuffer ();
		buff.append ("Name [").append (name).append ("]\n");
		buff.append ("Set [").append (setMethod).append ("]\n");
		buff.append ("Get [").append (getMethod).append ("]\n");
		if (getAccessorsCount () > 0) {
			buff.append ("Accessors : \n============\n");
		}
		for (int i = 0; i < getAccessorsCount (); i++) {
			buff.append ("Accessors " + i + " :\n");
			buff.append (getAccessor(i).toString ()).append ("\n");
		}
		return buff.toString ();
	}
	
	private void writeObject(ObjectOutputStream stream) throws IOException {
		stream.defaultWriteObject();
		SerializationUtils.writeMethod (stream, setMethod);
		SerializationUtils.writeMethod (stream, getMethod);
	}
	private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException, SecurityException, NoSuchMethodException {
		stream.defaultReadObject();
		setMethod = SerializationUtils.readMethod (stream);
		getMethod = SerializationUtils.readMethod (stream);
	}

	public void setType(Class type) {
		this.type = type;
	}
	
}